home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / ISSUE17 / THREADS / DBTHREAD.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-09-11  |  8.2 KB  |  258 lines

  1. unit DBThread;
  2. {
  3.   Author : Neil McClements
  4.   Date   : 2nd September 1996
  5.   C/right: (c) 1996 N. McClements
  6.   Purpose: Support for multi-threaded TQuery (using callbacks)
  7. }
  8.  
  9. interface
  10.  
  11. uses DB, DBTables, SysUtils,  Dialogs, Windows, cursor;
  12.  
  13. //
  14. // Define a method pointer type which can be used to return a populated TQuery to a callback procedure
  15. //
  16.   type TCallbackQuery=procedure (qry:TQuery) of object;
  17.  
  18. //
  19. // Define a wrapper for TDatabase, which adds thread-enabled query methods
  20. //
  21.   type TThreadDatabase=class(TDatabase)
  22.        private
  23.            ThreadsRunning:longint;
  24.            function Password:string;
  25.            function UserName:string;
  26.            function DBName:string;
  27.            function ServerName:string;
  28.            function Driver:string;
  29.            function CreateNewSession(const SessionName:string):TDatabase;
  30.            procedure ThreadDone(Sender:TObject);
  31.        public
  32.            function Connect:boolean;
  33.            function Disconnect:boolean;
  34.            function RunQuery(sql_str:string; var Callback:TCallBackQuery):THandle;
  35.   end;
  36.  
  37. //
  38. // Define a class to run the query within a thread, returning the result set as
  39. // a populated TQuery via a callback
  40. //
  41.   type TCallbackThread = class(TThread)
  42.        private
  43.            FCallback:TCallBackQuery;
  44.            FQuery: TQuery;
  45.            FSession: TSession;
  46.            FQueryException: Exception;
  47.            FCursor:TSpinningCursor;
  48.            procedure Execute; override;
  49.            procedure ReturnQueryToCallback;
  50.            procedure HandleExceptions;
  51.        public
  52.            constructor Create(Session:TSession; var Query:TQuery; var callback:TCallBackQuery);
  53.   end;
  54.  
  55. const
  56.    MAX_CONCURRENT_THREADS=10000;               // used as a seed for the random number SessionID generator
  57.  
  58. implementation
  59.  
  60. // #############################################################################
  61. // Class  : TThreadDatabase
  62. // Purpose: A TDatabase wrapper class
  63. // Author : Neil McClements
  64. // Date   : 2nd September '96
  65. // #############################################################################
  66.  
  67.  
  68. function TThreadDatabase.Password:string;
  69. // Return the password used to connect
  70. begin
  71.     Result:='masterkey';
  72. end;
  73.  
  74. function TThreadDatabase.UserName:string;
  75. // Return the database login for this user
  76. begin
  77.       Result:='SYSDBA';
  78. end;
  79.  
  80. function TThreadDatabase.DBName:string;
  81. // Return the name of the database
  82. begin
  83.     Result:='IBLOCAL';
  84. end;
  85.  
  86. function TThreadDatabase.ServerName:string;
  87. // Return the database file for connection
  88. begin
  89.     Result:='c:\delphi2\IntrBase\EXAMPLES\EMPLOYEE.GDB';
  90. end;
  91.  
  92. function TThreadDatabase.Driver:string;
  93. // Which driver we use to connect
  94. begin
  95.      Result:='INTRBASE';
  96. end;
  97.  
  98. function TThreadDatabase.CreateNewSession(const SessionName:string):TDatabase;
  99. var
  100.    tempdb:TDatabase;
  101.    newSession:TSession;
  102. begin
  103.      tempdb:=nil;
  104.      try
  105.        newSession:=Sessions.OpenSession(SessionName);
  106.        with Sessions do
  107.          begin
  108.            with FindSession(Sessionname) do
  109.               Result:=FindDatabase(SessionName);               // this database object exists already - so return it!
  110.            if Result=nil then
  111.              begin
  112.                tempdb:=TDatabase.create(self);
  113.                tempdb.drivername:=Driver;
  114.                tempdb.databasename:=(DBName+SessionName);
  115.                tempdb.sessionname:=SessionName;
  116.                tempdb.keepconnection:=false;
  117.                tempdb.loginprompt:=false;
  118.                tempdb.params.values['DATABASE NAME']:=DBName;
  119.                tempdb.params.values['SERVER NAME']:=ServerName;
  120.                tempdb.params.values['USER NAME']:=UserName;
  121.                tempdb.params.values['PASSWORD']:=Password;
  122.                tempdb.temporary:=true;    // connection perishes when parent is freed
  123.                try
  124.                  tempdb.connected:=true; // try and connect - if there's an error rtn nil
  125.                  Result:=tempdb;
  126.                except
  127.                 on e:EDBEngineError do
  128.                    begin
  129.                       tempdb.free;
  130.                       Result:=nil;
  131.                    end;
  132.                 end; // except
  133.              end; //if...
  134.          end; // outer with
  135.      except
  136.            tempdb.free;
  137.            Result:=nil;
  138.      end; // try...
  139. end;
  140.  
  141. procedure TThreadDatabase.ThreadDone(Sender:TObject);
  142. begin
  143.      showmessage('Thread ' + IntToStr((Sender as TThread).ThreadID) + ' finished!');
  144. end;
  145.  
  146. function TThreadDatabase.Connect:boolean;
  147. // Attempt to connect to the database. When tempdb is nil, the connection process has failed.
  148. // Returns true for successful connection. False indicates failure.
  149. var
  150.    tempdb:TDatabase;
  151. begin
  152.      tempdb:=CreateNewSession('Default'); // NB the first session created will be "Default"
  153.      if (tempdb=nil) then
  154.        Result:=false                   //error
  155.      else
  156.        begin
  157.           Result:=true;                // successful connection
  158.        end;
  159. end;
  160.  
  161. function TThreadDatabase.Disconnect:boolean;
  162. var
  163.    s:integer;
  164. begin
  165.      try
  166.        for s:=(sessions.count-1) downto 0 do
  167.            sessions[s].Databases[0].close;
  168.        disconnect:=true;
  169.      except
  170.           disconnect:=false;
  171.      end;
  172. end;
  173.  
  174. function TThreadDatabase.RunQuery(sql_str:string; var Callback:TCallBackQuery):THandle;
  175. var
  176.   NewSession:TSession;
  177.   ThreadQuery:TQuery;
  178.   SessionID:string;
  179.   thread_id:THandle;
  180.   SessionDB:TDatabase;
  181.   Thread:TCallBackThread;
  182. begin
  183.  
  184. // Use random numbers as a session identifier...
  185.      Randomize;
  186.      SessionID:=IntToStr(Random(MAX_CONCURRENT_THREADS));
  187.  
  188. // Create a new database, which can then be associated with the new session...
  189.      SessionDB:=CreateNewSession(SessionID);
  190.      NewSession:=Sessions.FindSession(SessionID);
  191.      if NewSession=nil then
  192.        begin
  193.          // there was an error creating the new database & session which wasn't flagged elsewhere - unlikely but...
  194.          showmessage('THREAD SESSION CREATION ERROR');
  195.      halt;
  196.        end;
  197.  
  198. // Create a new TQuery and connect it to the correct database with the newly created session
  199.      ThreadQuery:=TQuery.Create(Self);
  200.      with ThreadQuery do
  201.        begin
  202.          DatabaseName:=(DBName+SessionID);  //maintain that reference - eg 'IBLOCAL1645'
  203.          SessionName:=SessionID;
  204.          close;
  205.          sql.add(sql_str);
  206.        end; // with
  207.  
  208. // Kick-off a thread with the session, query and callback procedure passed as parameters to the thread's constructor
  209.      thread:=TCallBackThread.Create(NewSession, ThreadQuery, Callback);
  210.      thread.OnTerminate:=ThreadDone;
  211.      thread_id:=((thread as TThread).threadid);
  212.      Result:= thread_id; // Return the thread id - in case user wants to cancel query, manipulate thread with API etc
  213. end;
  214.  
  215.  
  216. // #############################################################################
  217. // Class  : TCallBackThread
  218. // Purpose: Implementation of a TThread-derived threaded query class
  219. // Author : Neil McClements
  220. // Date   : 2nd September '96
  221. // #############################################################################
  222.  
  223. constructor TCallbackThread.Create(Session:TSession;  var Query:TQuery; var callback:TCallBackQuery);
  224. begin
  225.   inherited Create(true);    // create thread in a suspended state
  226.   Priority:=tpNormal;
  227.   FCursor:=TSpinningCursor.Create;
  228.   FSession:=Session;
  229.   FQuery:=Query;
  230.   FCallback:=callback;
  231.   FreeOnTerminate:=True;
  232.   Resume;                    // initialisation complete so kick-off the thread
  233. end;
  234.  
  235. procedure TCallbackThread.Execute;
  236. begin
  237.   try
  238.     FQuery.Open;                        // execute the query - this may take some time
  239.     Synchronize(ReturnQueryToCallback); // once the query has finished, return the result set
  240.   except
  241.     FQueryException := ExceptObject as Exception;
  242.     Synchronize(HandleExceptions);
  243.   end;
  244. end;
  245.  
  246. procedure TCallbackThread.ReturnQueryToCallback;
  247. begin
  248.      FCursor.Terminate;  // zap the cursor thread!
  249.      FCallback(FQuery);  // return the TQuery result set to the callback proc
  250. end;
  251.  
  252. procedure TCallbackThread.HandleExceptions;
  253. begin
  254.      FCursor.terminate; // zap the cursor thread and report the error
  255.      showmessage('Error running query :' + FQueryException.message);
  256. end;
  257.  
  258. end.